iT邦幫忙

2024 iThome 鐵人賽

DAY 7
0
Software Development

螃蟹幼幼班:Rust 入門指南系列 第 7

Day7 - 型別:元組與陣列

  • 分享至 

  • xImage
  •  

複合型別可以組合數個數值為一個型別,Rust 有兩個基本複合型別:元組(tuples)和陣列(arrays)。

元組

元組擁有固定長度,可以將許多不同型別的數值合成一個複合型別。

要建立元組就是用一組小括號涵蓋不同值,每個值再用逗號分隔開來。
元組的每一格都是一個獨立型別,不同數值不必是相同型別。

可以自行詮釋型別:

fn main() {
    let my_tuple: (i32, &str, f64, bool) = (1, "ball", -4.3, false);
    println!("{:?}", my_tuple);
}

也可以讓 Rust 判斷型別:

fn main() {
    let my_tuple = (1, "ball", -4.3, false);
    println!("{:?}", my_tuple); 
}
$ cargo run
(1, "ball", -4.3, false)

元素組合不限於純量,基本複合型別也可以:

fn main() {
    let tuple = (1, (2, 3), [1; 3],  "text");
    println!("{:?}", tuple); // (1, (2, 3), [1, 1, 1], "text")
}

要取得元素中某個數值的話可以用解構(Destructuring)的方式:

fn main() {
    let my_tuple = (1, "ball", -4.3, false);
    let (a, b, c, d) = my_tuple;
    println!("{}", a)
}

這段程式碼會跳 waring 提示我們沒用到的變數可以加入前綴 _
不像 JavaScript 需要額外安裝 eslint 來判斷,Rust 內建這種檢查。

$ cargo run
warning: unused variable: `b`
 --> src/main.rs:3:13
  |
3 |     let (a, b, c, d) = my_tuple;
  |             ^ help: if this is intentional, prefix it with an underscore: `_b`
  |
  = note: `#[warn(unused_variables)]` on by default

warning: unused variable: `c`
 --> src/main.rs:3:16
  |
3 |     let (a, b, c, d) = my_tuple;
  |                ^ help: if this is intentional, prefix it with an underscore: `_c`

warning: unused variable: `d`
 --> src/main.rs:3:19
  |
3 |     let (a, b, c, d) = my_tuple;
  |                   ^ help: if this is intentional, prefix it with an underscore: `_d`

warning: `types_tuple` (bin "types_tuple") generated 3 warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.13s
     Running `target/debug/types_tuple`
1

如果解構的變數數量和 tuple 的長度不相符編譯會報錯,數量多或少都不行。

$ cargo run
error[E0308]: mismatched types
 --> src/main.rs:3:9
  |
3 |     let (a, b, c, d, e) = my_tuple;
  |         ^^^^^^^^^^^^^^^   -------- this expression has type `(i32, &str, f64, bool)`
  |         |
  |         expected a tuple with 4 elements, found one with 5 elements
  |
  = note: expected tuple `(i32, &str, f64, bool)`
             found tuple `(_, _, _, _, _)`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `types_tuple` (bin "types_tuple") due to 1 previous error
$ cargo run
error[E0308]: mismatched types
 --> src/main.rs:3:9
  |
3 |     let (a, b) = my_tuple;
  |         ^^^^^^   -------- this expression has type `(i32, &str, f64, bool)`
  |         |
  |         expected a tuple with 4 elements, found one with 2 elements
  |
  = note: expected tuple `(i32, &str, f64, bool)`
             found tuple `(_, _)`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `types_tuple` (bin "types_tuple") due to 1 previous error

除了解構之外,另一種取得某個元素數值的方式是元組索引法(Tuple Indexing),用句號(.)加上數值索引來訪問元組內的元素。索引一樣是從 0 開始。

fn main() {
    let my_tuple = (1, "ball", -4.3, false);
    println!("{}", my_tuple.0);
}
$ cargo run
1

而且一樣如果索引值超過範圍會報錯。

$ cargo run
error[E0609]: no field `4` on type `({integer}, &str, {float}, bool)`
 --> src/main.rs:3:29
  |
3 |     println!("{}", my_tuple.4);
  |                             ^ unknown field

For more information about this error, try `rustc --explain E0609`.
error: could not compile `types_tuple` (bin "types_tuple") due to 1 previous error

單元型別

另外沒有任何數值的元組在 Rust 裡有特殊意義,叫做單元型別(Unit),其數值與型別都用 () 表示,代表「沒有有用的值」或「空的值」。如果一個表達式沒有回傳值,Rust 會隱式回傳單元型別,這在函數沒有回傳值時很常見:

fn do_nothing() {
    println!("Do nothing here")
}

fn main() {
    let result = do_nothing(); // result 的型別是 ()
    println!("result of do_nothing: {:?}", result);
}
$ cargo run
Do nothing here
result of do_nothing: ()

這也解釋了為什麼當初寫的 echo function ,如果執行成功的話,Ok 裡面放的是 ()

陣列

陣列和元組的主要差別是,陣列裡的每一個元素要是相同的型別,另一方面也和元組一樣,長度固定不可變的。

和其他程式語言的陣列比較像的型別是向量(vector),它是標準函式庫(std)提供的集合型別,類似於陣列但允許變更長度大小,這邊先不深究,只要有個印象 Rust 的陣列是被配置在 stack 上且已知固定大小的一整塊記憶體;而向量的資料本體存在 heap,所以陣列的效能比向量更好,但就比較不彈性(長度固定),需要根據需求決定用哪種型別:

  • 如果需要固定大小和最佳效能,選擇陣列。
  • 如果需要動態大小和更多彈性,選擇向量。

要宣告一個陣列的話是用中括號涵蓋不同值,每個值再用逗號分隔開來。

fn main() {
    let my_array = ["to_do", "in_progress", "done"];
    println!("{:?}", my_array);
}
$ cargo run
["to_do", "in_progress", "done"]

取得元素數值的方式和元組大致相同。

可以用索引來取得陣列的元素:

fn main() {
    let my_array = ["to_do", "in_progress", "done"];
    println!("{}", my_array[0]);
}

也可以用解構的方式:

fn main() {
    let my_array = ["to_do", "in_progress", "done"];
    let [init_status, _, _] = my_array;
    println!("{}", init_status);
}

另外陣列有一種寫法叫作重複表達式(Repeat Expression),用法是在中括號裡面指定一個數值後加上分號。這提供了一種簡潔的方式來初始化所有元素為相同值的陣列,與手動重複多次相同值的寫法相比更加簡便。

fn main() {
    let manual = [3, 3, 3, 3, 3];
    let repeat = [3; 5];
    println!("{:?}", manual);
    println!("{:?}", repeat);
}

兩者顯示的結果是一樣的。

結語

不論是元組或是陣列,如果嘗試使用索引存取元素時,Rust 會檢查這個索引是有效的,如果無效的話 Rust 會立即離開程式避免存取到邊界外的記憶體,這是 Rust 基於記憶體安全原則給予的保障。在許多低階語言並不會提供這樣的檢查,所以當提供不正確的索引時,無效的記憶體可能會被存取,例如著名的心臟出血漏洞(Heartbleed bug)就是其中一個案例,這個漏洞在沒有檢查記憶體邊界的情況下,導致了敏感數據可能被洩露,有興趣可以看看。

Attackers can send Heartbeat requests with the value of the length field greater than the actual length of the payload. OpenSSL processes in the machine that are responding to Heartbeat requests don’t verify if the payload size is same as what is specified in length field. Thus, the machine copies extra data residing in memory after the payload into the response. This is how the Heartbleed vulnerability works. Therefore, the extra bytes are additional data in the remote process’s memory.


上一篇
Day6 - 型別:字元、布林值
下一篇
Day8 - 變數與常數
系列文
螃蟹幼幼班:Rust 入門指南25
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言